﻿using Painter3D.RenderSys;
using System;
using System.Xml;
using Painter3D.RenderSys.RenderObject;

namespace Painter3D.RenderSys.RenderObject
{
    class Font
    {
        public struct CharLayout
        {
            public int id;
            public int x;
            public int y;
            public int width;
            public int height;
            public int xoffset;
            public int yoffset;
            public int xadvance;

            public CharLayout(int id, int x, int y, int width, int height, int xoffset, int yoffset, int xadvance)
            {
                this.id = id;
                this.x = x;
                this.y = y;
                this.width = width;
                this.height = height;
                this.xoffset = xoffset;
                this.yoffset = yoffset;
                this.xadvance = xadvance;
            }
        }
        public struct Kerning
        {
            public int id1;
            public int id2;
            public int amount;

            public Kerning(int id1, int id2, int amount)
            {
                this.id1 = id1;
                this.id2 = id2;
                this.amount = amount;
            }
        }

        public readonly Texture2D texture;
        readonly int textureWidth;
        readonly int textureHeight;
        public readonly int charBase;
        readonly int lineHeight;
        public readonly string name;
        readonly CharLayout[] charLayouts;
        readonly Kerning[] kernings;


        /// <summary>
        /// 根据预生成的字体贴图生成一个字库
        /// </summary>
        /// <param name="descriptionPath">定位文件</param>
        /// <param name="texturePath">材质文件</param>
        public Font(string descriptionPath, string texturePath)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(descriptionPath);
            if (doc == null)
                throw new Exception("XML解析错误" + descriptionPath);
            XmlNode fontN = doc.SelectSingleNode("font");
            if (fontN == null)
                throw new Exception("XML解析错误<font>丢失");
            XmlElement infoE = (XmlElement)fontN.SelectSingleNode("info");
            if (infoE == null)
                throw new Exception("XML解析错误<font/info>丢失");
            name = infoE.GetAttribute("face");
            XmlElement commonE = (XmlElement)fontN.SelectSingleNode("common");
            if (commonE == null)
                throw new Exception("XML解析错误<font/common>丢失");
            textureWidth = int.Parse(commonE.GetAttribute("scaleW"));
            textureHeight = int.Parse(commonE.GetAttribute("scaleH"));
            lineHeight = int.Parse(commonE.GetAttribute("lineHeight"));
            charBase = int.Parse(commonE.GetAttribute("base"));
            //处理字符
            XmlElement charsE = (XmlElement)fontN.SelectSingleNode("chars");
            if (charsE == null)
                throw new Exception("XML解析错误<font/chars>丢失");
            int charCount = int.Parse(charsE.GetAttribute("count"));
            if (charCount <= 0)
                throw new Exception("XML解析错误<font/chars/char>数量不足" + charCount);
            charLayouts = new CharLayout[charCount];
            XmlNodeList charsNL = charsE.ChildNodes;
            for (int i = 0; i < charCount; i++)
            {
                XmlElement charE = (XmlElement)charsNL[i];
                charLayouts[i].id = int.Parse(charE.GetAttribute("id"));
                charLayouts[i].x = int.Parse(charE.GetAttribute("x"));
                charLayouts[i].y = int.Parse(charE.GetAttribute("y"));
                charLayouts[i].width = int.Parse(charE.GetAttribute("width"));
                charLayouts[i].height = int.Parse(charE.GetAttribute("height"));
                charLayouts[i].xoffset = int.Parse(charE.GetAttribute("xoffset"));
                charLayouts[i].yoffset = int.Parse(charE.GetAttribute("yoffset"));
                charLayouts[i].xadvance = int.Parse(charE.GetAttribute("xadvance"));
            }
            //处理补偿
            XmlElement kerningsE = (XmlElement)fontN.SelectSingleNode("kernings");
            if (kerningsE == null)
                throw new Exception("XML解析错误<font/kernings>丢失");
            int kerningsCount = int.Parse(kerningsE.GetAttribute("count"));
            if (charCount <= 0)
                throw new Exception("XML解析错误<font/kernings/kerning>数量不足" + kerningsCount);
            kernings = new Kerning[kerningsCount];
            XmlNodeList kerningsNL = kerningsE.ChildNodes;
            for (int i = 0; i < kerningsCount; i++)
            {
                XmlElement kerningE = (XmlElement)kerningsNL[i];
                kernings[i].id1 = int.Parse(kerningE.GetAttribute("first"));
                kernings[i].id2 = int.Parse(kerningE.GetAttribute("second"));
                kernings[i].amount = int.Parse(kerningE.GetAttribute("amount"));
            }

            //加载图像
            texture = new Texture2D(texturePath);
        }

        /// <summary>
        /// 生成一个用于空间效果的字符对象，该对象被缩放50倍以避免过大
        /// </summary>
        /// <param name="message">信息</param>
        /// <returns>顶点数据</returns>
        public VertexData VertexData3D(string message)
        {
            GenerateVertexData(message, out float[] pos, out float[] uv, out uint[] ind);
            for (int i = 0; i < pos.Length; i++)
            {
                pos[i] /= 50.0f;//3D字体不缩放会变得太大
            }
            return new ElementVertexData(new ShaderInfo.VertexRequestTagEnum[]
            {
                ShaderInfo.VertexRequestTagEnum.Position,
                ShaderInfo.VertexRequestTagEnum.UV1,
            }, new float[][] {
                pos,uv
            }, ind);
        }

        /// <summary>
        /// 生成一个用于UI效果的字符对象
        /// </summary>
        /// <param name="message">信息</param>
        /// <returns>顶点数据</returns>
        public VertexData VertexData2D(string message)
        {
            GenerateVertexData(message, out float[] pos, out float[] uv, out uint[] ind);
            return new ElementVertexData(new ShaderInfo.VertexRequestTagEnum[]
            {
                ShaderInfo.VertexRequestTagEnum.Position,
                ShaderInfo.VertexRequestTagEnum.UV1,
            }, new float[][] {
                pos,uv
            }, ind);
        }

        private void GenerateVertexData(char c, out float[] position, out float[] UV, out uint[] index, out int advance)
        {
            CharLayout layout = new CharLayout();
            foreach (var charLayout in charLayouts)
            {
                if (charLayout.id == c)
                {
                    layout = charLayout;
                    break;
                }
            }
            position = new float[]
            {
                layout.xoffset,                 charBase-layout.yoffset-layout.height,  0.0f,
                layout.width+layout.xoffset,    charBase-layout.yoffset-layout.height,  0.0f,
                layout.width+layout.xoffset,    charBase-layout.yoffset,                0.0f,
                layout.xoffset,                 charBase-layout.yoffset,                0.0f,
            };
            UV = new float[]
            {
                (float)(layout.x)                  /textureWidth, 1-((float)(layout.y+layout.height)  /textureHeight),
                (float)(layout.x + layout.width)   /textureWidth, 1-((float)(layout.y+layout.height)  /textureHeight),
                (float)(layout.x + layout.width)   /textureWidth, 1-((float)(layout.y)                /textureHeight),
                (float)(layout.x)                  /textureWidth, 1-((float)(layout.y)                /textureHeight),
            };
            index = new uint[]
            {
                0,1,2,
                0,2,3
            };
            advance = layout.xadvance;
        }

        private void GenerateVertexData(string message, out float[] position, out float[] UV, out uint[] index)
        {
            position = new float[message.Length * 12];
            UV = new float[message.Length * 8];
            index = new uint[message.Length * 6];
            int PosX = 0;
            for (int i = 0; i < message.Length; i++)
            {
                GenerateVertexData(message[i], out float[] pos, out float[] uv, out uint[] ind, out int advance);
                for (int k = 0; k < pos.Length; k++)
                {
                    if (k % 3 == 0)
                        pos[k] += PosX;
                }
                pos.CopyTo(position, i * 12);
                uv.CopyTo(UV, i * 8);
                for (int k = 0; k < ind.Length; k++)
                {
                    ind[k] += (uint)i * 4;
                }
                ind.CopyTo(index, i * 6);
                foreach (var kerning in kernings)
                {
                    if (i < message.Length-1 && kerning.id1 == message[i] && kerning.id2 == message[i + 1])
                    {
                        PosX += kerning.amount;
                        break;
                    }
                }
                PosX += advance;
            }
        }

    }
}
